#include <fstream>
#include <sstream>
#include <cmath>

#include "bga/graphics/ObjFileLoader.h"
#include "bga/graphics/Drawable.h"

using namespace std;

namespace bga
{

ObjFileLoader::ObjFileLoader() : Loader<Drawable>()
{

}

ObjFileLoader::ObjFileLoader(const ObjFileLoader& loaderCopy) : listVertex(loaderCopy.listVertex),
                                                                listTexCoord(loaderCopy.listTexCoord),
                                                                listNormals(loaderCopy.listNormals),
                                                                listIndices(loaderCopy.listIndices)
{

}

ObjFileLoader::~ObjFileLoader()
{

}

Drawable* ObjFileLoader::load(File& file)
{
    Drawable* d = new Drawable();
    string s;

    /*std::ifstream file;
    file.open(filename.c_str(), std::ios::in);
    if(file.is_open())*/
    file.open();    /// We open the file if it is not
    //std::ifstream* stream = file.getStream();
    {
        while(file >> s)
        {
            if(s == "v")
            {
                OBJTripletFloat t;
                file >> t[0];
                file >> t[1];
                file >> t[2];

                this->listVertex.push_back(t);
            }
            else if(s == "vn")
            {
                OBJTripletFloat t;
                file >> t[0];
                file >> t[1];
                file >> t[2];

                this->listNormals.push_back(t);
            }
            else if(s == "vt")
            {
                OBJDoubletFloat d;
                file >> d[0];
                file >> d[1];

                this->listTexCoord.push_back(d);
            }
            else if(s == "f")
            {
                string s;

                for(unsigned short i = 0; i < 3; i ++)
                {
                    file >> s;

                    OBJTripletUS t = getIndices(s);

                    this->listIndices.push_back(t);
                }
            }
        }
        file.close();
    }
    /// Fill the drawable
    this->getDrawable(d);
    this->reset();
    return d;
}

void ObjFileLoader::getDrawable(Drawable* drawable)
{
    /// fake texture array
    /**float coord[nbVertex * 2];

    float colors[nbVertex * 3];*/

    std::vector<OBJTripletFloat>::iterator itTripletFloat;
    std::vector<OBJTripletUS>::iterator itTripletUS;

    /// Building the arrays
    float vertexArray[this->listVertex.size() * 3];
    float normalArray[this->listVertex.size() * 3];
    //float texCoordArray[/*this->listVertex.size() * 2*/] = {0, 0, 0, 1, 1, 1, 1,0};
    float texCoordArray[this->listVertex.size() * 2];
    unsigned short indicesArray[this->listIndices.size()];

    for(unsigned int i = 0; i < this->listVertex.size() * 3; i++)
    {
        normalArray[i] = 0.f;
    }

    unsigned int currentIndex = 0;
    for(itTripletFloat = this->listVertex.begin(); itTripletFloat != this->listVertex.end(); ++itTripletFloat)
    {
        vertexArray[currentIndex++] = (*itTripletFloat)[0];
        vertexArray[currentIndex++] = (*itTripletFloat)[1];
        vertexArray[currentIndex++] = (*itTripletFloat)[2];
    }

    currentIndex = 0;
    for(itTripletUS = this->listIndices.begin(); itTripletUS != this->listIndices.end(); ++itTripletUS)
    {
        indicesArray[currentIndex++] = (*itTripletUS)[0] - 1;

        if(!this->listTexCoord.empty())
        {
            texCoordArray[((*itTripletUS)[0] - 1) * 2] = listTexCoord[(*itTripletUS)[1] - 1][0];
            texCoordArray[((*itTripletUS)[0] - 1) * 2 + 1] = listTexCoord[(*itTripletUS)[1] - 1][1];
        }



        if(!this->listNormals.empty())
        {
            normalArray[((*itTripletUS)[0] - 1) * 3] += listNormals[(*itTripletUS)[2] - 1][0];
            normalArray[((*itTripletUS)[0] - 1) * 3 + 1] += listNormals[(*itTripletUS)[2] - 1][1];
            normalArray[((*itTripletUS)[0] - 1) * 3 + 2] += listNormals[(*itTripletUS)[2] - 1][2];
        }

    }

    /// Step of normalization of normals
    for(unsigned int i = 0; i < this->listVertex.size(); i++)
    {
        double norm = sqrt(normalArray[i * 3] * normalArray[i * 3] +
                        normalArray[i * 3 + 1] * normalArray[i * 3 + 1] +
                        normalArray[i*3 + 2] * normalArray[i*3 + 2]);

        normalArray[i * 3] /= norm;
        normalArray[i * 3 + 1] /= norm;
        normalArray[i * 3 + 2] /= norm;
    }


    drawable->setPrimitive(GL_TRIANGLES);
    drawable->setVertexArray(vertexArray, this->listVertex.size() * 3);
    if(!this->listNormals.empty())
    {
        drawable->setNormalsArray(normalArray, this->listVertex.size() * 3);
    }
    if(!this->listTexCoord.empty())
    {
        drawable->setTexCoordArray(texCoordArray, this->listVertex.size() * 2);
    }

    drawable->setIndexArray(indicesArray, this->listIndices.size());

}

ObjFileLoader::OBJTripletUS ObjFileLoader::getIndices(const string& s)
{
    OBJTripletUS tuple;
    string tmp;
    unsigned short j = 0;
    short val;
    for(unsigned int i = 0; i < s.length(); i++)
    {
        if(s.at(i) != '/')
        {
            tmp.push_back(s.at(i));
        }
        else
        {
            if(!tmp.empty())
            {
                istringstream stream;
                stream.str(tmp);
                stream >> val;
                tuple[j] = val;
                tmp.clear();
            }
            j++;
        }
    }

    if(!tmp.empty())
    {
        istringstream stream;
        stream.str(tmp);
        stream >> val;
        tuple[j] = val;
        tmp.clear();
    }

    return tuple;
}

void ObjFileLoader::reset()
{
    this->listVertex.clear();
    this->listTexCoord.clear();
    this->listNormals.clear();
    this->listIndices.clear();
}

}
